#include <ani.h>
#include <iostream>
#include <array>

class Context {
    public:
        std::string getTempDir(){
            return std::string("/usr/tmp");
        }

        void startAbility(){
            std::cout << "start ability" << std::endl;
        }

        ~Context(){
            std::cout << "~Context destroyed" << std::endl;
        }
};

static Context * unwrapp(ani_env *env, ani_object object){
    //TODO 改成get object type
    ani_long context;
    if(ANI_OK != env->Object_GetFieldByName_Long(object, "nativeContext", &context)){
        return nullptr;
    }
    return reinterpret_cast<Context *>(context);
}


static void startAbilitySync([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object){
    auto context =  unwrapp(env,  object);
    if(context != nullptr){
        context->startAbility();
    }
}

static ani_string getTempDirSync([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object){
    
    //TODO 如何返回一个空的 string
    auto context =  unwrapp(env,  object);
    
    auto dir = context->getTempDir();
    ani_string ret;
    env->String_NewUTF8(dir.c_str(), dir.size(), &ret);
    return ret;
}

static ani_object create([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class clazz){
    auto nativeContext = new Context();

    static const char *className = "ani_wrap_native_ptr.Contex";
    ani_class cls;
    if (ANI_OK != env->FindClass(className, &cls)) {
        std::cerr << "Not found '" << className << "'" << std::endl;
        ani_object nullobj = {};
        return nullobj;
    }

    ani_method ctor;
    if(ANI_OK != env->Class_FindMethod(cls, "<ctor>", "l:", &ctor)){
        std::cerr << "Not found '" << "ctor" << "'" << std::endl;
        ani_object nullobj = {};
        return nullobj;
    }

    ani_object context_object;
    if(ANI_OK !=env->Object_New(cls, ctor, &context_object, reinterpret_cast<ani_long>(nativeContext)))
    {   
        std::cerr << "New Context Fail" << std::endl;
    }
    return context_object;
}

static void clean([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object){
    ani_long ptr;
    if(ANI_OK != env->Object_GetFieldByName_Long(object, "ptr", &ptr)){
        return;
    }
    delete reinterpret_cast<Context *>(ptr);
}

static ani_status BindContext(ani_env *env){
    static const char *className = "ani_wrap_native_ptr.Contex";
    ani_class cls;
    if (ANI_OK != env->FindClass(className, &cls)) {
        std::cerr << "Not found '" << className << "'" << std::endl;
        return (ani_status)ANI_ERROR;
    }

    static const char *CleanerName = "ani_wrap_native_ptr.Cleaner";
    ani_class Cleanercls;
    if (ANI_OK != env->FindClass(CleanerName, &Cleanercls)) {
        std::cerr << "Not found '" << CleanerName << "'" << std::endl;
        return (ani_status)ANI_ERROR;
    }

    std::array methods = {
        ani_native_function {"startAbilitySync", ":", reinterpret_cast<void *>(startAbilitySync) },
        ani_native_function {"getTempDirSync", ":C{std.core.String}", reinterpret_cast<void *>(getTempDirSync) },
        ani_native_function {"create", ":C{ani_wrap_native_ptr.Contex}", reinterpret_cast<void *>(create) },
    };

    std::array CleanerMethods = {
        ani_native_function {"clean", nullptr, reinterpret_cast<void *>(clean) },
    };

    if (ANI_OK != env->Class_BindNativeMethods(cls, methods.data(), methods.size())) {
        std::cerr << "Cannot bind native methods to '" << className << "'" << std::endl;
        return (ani_status)ANI_ERROR;
    };

    if (ANI_OK != env->Class_BindNativeMethods(Cleanercls, CleanerMethods.data(), CleanerMethods.size())) {
        std::cerr << "Cannot bind native methods to '" << CleanerName << "'" << std::endl;
        return (ani_status)ANI_ERROR;
    };

    return ANI_OK;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    ani_env *env;
    if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
        std::cerr << "Unsupported ANI_VERSION_1" << std::endl;
        return (ani_status)ANI_ERROR;
    }
    
    auto  status = BindContext(env);
    if(status != ANI_OK){
        return status;
    }
    *result = ANI_VERSION_1;
    return ANI_OK;
}